/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
//								Includes
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

#include "Types.h"
#include <WinSock2.h>
#include "ServerClientTools.h"
#include "SocketSendRecvTools.h"
#include "GeneralFunctions.h"

#define DEBUG_MODE

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
//							END OF Includes
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
//						Internal Functions Declarations
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/

/*
 * NotifyNodeOnBeginningOfRound
 * =========================
 * Function Description: This function notifies all nodes that a round has begun.
 * Arguments: NodesSockets		An array of the sockets of all the nodes.
 * 			  NumOfNodes		The number of nodes.
 * Return Value: TRUE on success, FALSE on failure.
 */
BOOL NotifyAllNodesOnBeginningOfRound(SOCKET *NodesSockets, int NumOfNodes);

/*
 * NotifyNodeOnBeginningOfRound
 * =========================
 * Function Description: This function notifies a single node that a round has begun.
 * Arguments: NodeSocket		The node's socket.
 */
void NotifyNodeOnBeginningOfRound(SOCKET *NodeSocket);

/*
 * WaitAllNodesToNotifyEndOfRound
 * =========================
 * Function Description: This function starts NumOfNodes threads, each waiting for a single node to 
 *                       notify that it has finished its work for this round. Only after 
 *                       all nodes have finished the round the function returns and the round ends. 
 * Arguments: NodesSockets		An array of the sockets of all the nodes.
 * 			  NumOfNodes		The number of nodes.
 * Return Value: TRUE on success, FALSE on failure.
 */
BOOL WaitAllNodesToNotifyEndOfRound(SOCKET *NodesSockets, int NumOfNodes);

/*
 * WaitForNodeToNotifyEndOfRound
 * =========================
 * Function Description: This function is the main function of the thead that waits for a single node to 
 *                       notify that the round has ended.
 * Arguments: NodeSocket		The node's socket
 */
void WaitForNodeToNotifyEndOfRound(SOCKET *NodeSocket);

/*
 * CheckIfClientsAreConnected
 * =========================
 * Function Description: This function checks if clients are connected to the server (Meanning that not all clients have disconnected from the server)..
 * Arguments: NodesSockets		An array with all the nodes sockets.
 * 			  NumOfNodes        The number of nodes.
 * Return Value: TRUE if there is at least one connected client. FALSE if all clients have disconnected.
 */
BOOL CheckIfClientsAreConnected(SOCKET *NodesSockets, int NumOfNodes);


/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
//					END OF Internal Functions Declarations
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/


/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
//						Global Variables Decleraction
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
int RoundNumber;
int NumOfConnectedClients;

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
//						Main Clock Function
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
void MainClock(int argc, char *argv[])
{
	char *SelfIP = argv[2];
	int SelfPort = atoi(argv[3]);
	int NumOfNodes = atoi(argv[4]);
	int i;
	int RegisteredNodes=0;
	SOCKET MainSocket;
	SOCKET *NodesSockets = NULL;
	HANDLE *NodeThreadHandles = NULL;

	NodesSockets = (SOCKET*)malloc(NumOfNodes*sizeof(SOCKET));
	if (NULL == NodesSockets)
	{
		//TODO
		PrintErrorAndExit(ERROR_ALLOCATING_MEMORY);
	}

	RoundNumber = 0;


	//Accept connections from nodes. Start a server.
	MainSocket = StartServer(SelfIP, SelfPort);

	// Register all nodes
	for (i = 0 ; i < NumOfNodes ; i++)
	{
		NodesSockets[i] = AcceptConnection(MainSocket);
		RegisteredNodes++;
		printf("Number of Registered nodes: %d\n", RegisteredNodes);
	}
	NumOfConnectedClients = NumOfNodes;
	printf("All nodes finished registering\n");
	FinishedStartUpSequence();	

	/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
	
	while (1)
	{
		RoundNumber++;
		NotifyAllNodesOnBeginningOfRound(NodesSockets, NumOfNodes);
		WaitAllNodesToNotifyEndOfRound(NodesSockets, NumOfNodes);
	}
}

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
//						END OF Main Clock Function
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/


/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
//						Internal Functions Implementations
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
BOOL NotifyAllNodesOnBeginningOfRound(SOCKET *NodesSockets, int NumOfNodes)
{
	int i;
	DWORD WaitRes;
	HANDLE *NodeThreadHandles  = (HANDLE*)malloc(NumOfNodes*sizeof(HANDLE));
	if (NULL == NodeThreadHandles)
	{
		PrintErrorAndExit(ERROR_ALLOCATING_MEMORY);
	}
	
	for (i=0 ; i< NumOfNodes ; i++)
	{
		NodeThreadHandles[i] = CreateThreadSimple((LPTHREAD_START_ROUTINE)NotifyNodeOnBeginningOfRound, NodesSockets+i, NULL);
	}

	WaitRes = WaitForMultipleObjects(NumOfNodes, NodeThreadHandles, TRUE, INFINITE);
	free(NodeThreadHandles);
	if (WaitRes == WAIT_OBJECT_0)
	{
		printf("Notified all nodes on beginning of round no. %d\n", RoundNumber);
		PrintRoundStarted(RoundNumber);
		return TRUE;
	}
	else
	{
		PrintErrorAndExit(ERROR_WAITING_FOR_MULTIPLE_OBJECT);
		return FALSE;
	}
}


void NotifyNodeOnBeginningOfRound(SOCKET *NodeSocket)
{
	if (*NodeSocket == -1)
		return;

	SendActionType(ACTION_BEGINNING_OF_ROUND, *NodeSocket);
	SendInteger(RoundNumber, *NodeSocket);
}

BOOL WaitAllNodesToNotifyEndOfRound(SOCKET *NodesSockets, int NumOfNodes)
{
	int i;
	DWORD WaitRes;
	HANDLE *NodeThreadHandles  = (HANDLE*)malloc(NumOfNodes*sizeof(HANDLE));
	TActionType ReceivedActionType;
	if (CheckIfClientsAreConnected(NodesSockets, NumOfNodes) == FALSE)
	{
		PrintEndOfGame();
		exit(1);
	}
	for (i=0 ; i< NumOfNodes ; i++)
	{
			NodeThreadHandles[i] = CreateThreadSimple((LPTHREAD_START_ROUTINE)WaitForNodeToNotifyEndOfRound, NodesSockets+i, NULL);
	}
	WaitRes = WaitForMultipleObjects(NumOfNodes, NodeThreadHandles, TRUE, INFINITE);
	if (WaitRes == WAIT_OBJECT_0)
	{
		PrintRoundEnded();
	}
	else
	{
		PrintErrorAndExit(ERROR_WAITING_FOR_MULTIPLE_OBJECT);
		return FALSE;
	}
	free(NodeThreadHandles);
	return TRUE;
}

void WaitForNodeToNotifyEndOfRound(SOCKET *NodeSocket)
{
	TActionType ReceivedAction;
	TransferResult_t RecvRes;
	if (*NodeSocket == -1)
		return;

	RecvRes = ReceiveActionType(&ReceivedAction, *NodeSocket);
	if (RecvRes == TRNS_DISCONNECTED)
	{
		*NodeSocket = -1;
		printf("A client closed the connection.\n");
		//NumOfConnectedClients--;
	}
	else
	{
		if (ReceivedAction != ACTION_END_OF_ROUND_TO_CLOCK)
		{
			PrintErrorAndExit(GENERAL_ERROR);
		}
	}
}

BOOL CheckIfClientsAreConnected(SOCKET *NodesSockets, int NumOfNodes)
{
	int i;
	for (i = 0; i < NumOfNodes; i++)
	{
		if (NodesSockets[i] != -1)
			return TRUE;
	}
	return FALSE;
}

/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/
//				END OF Internal Functions Implementations
/*oOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoOoO*/